﻿#region Using Statemens
using System;
using System.Collections.Generic;
using System.Diagnostics;
using GameBase.Screens;
using GameBase.Sprites;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StarFox.ScreenComponents;
#endregion

namespace StarFox.Screens
{
    /// <summary>
    /// Basic properties needed to implement any game menu.
    /// </summary>
    internal class GamePlayMenu : ScreenComponent2D
    {
        #region Types

        /// <summary>
        /// Possible button messages associated to a menu item.
        /// </summary>
        public enum ButtonMessage
        {
            /// <summary>
            /// Perform the action indicated by the menu item.
            /// </summary>
            SELECT,
            /// <summary>
            /// Go back to the previous menu screen.
            /// </summary>
            BACK,
        };

        #region Menu Items

        /// <summary>
        /// Defines basic functionality needed for every menu item.
        /// </summary>
        public interface IMenuItem
        {
            /// <summary>
            /// Gets the label associated to a menu item.
            /// </summary>
            string Label { get; }

            /// <summary>
            /// Gets the button messages associated to a menu item.
            /// </summary>
            ButtonMessage[] Buttons { get; }
        }

        /// <summary>
        /// Stores all necessary information for a link menu item.
        /// </summary>
        public class LinkItem : IMenuItem
        {
            // backing fields
            string label;
            ButtonMessage[] buttons;

            /// <summary>
            /// Gets the label associated to a menu item.
            /// </summary>
            public string Label { get { return label; } }

            /// <summary>
            /// Gets the button messages associated to a menu item.
            /// </summary>
            public ButtonMessage[] Buttons { get { return buttons; } }

            /// <summary>
            /// Creates a new link menu item.
            /// </summary>
            /// <param name="label">Label associated to the menu item.</param>
            /// <param name="buttons">Button messages associated to the menu item.</param>
            public LinkItem(string label, ButtonMessage[] buttons)
            {
                this.label = label;
                this.buttons = buttons;
            }
        }

        #endregion

        #endregion

        #region Fields

        // sprite sheet
        SpriteSheet spriteSheet;

        // starfox logo
        Rectangle dstRectStarFox;
        Rectangle dstRectXna;

        // menu title
        string menuTitle;
        Point initPosMenuTitle;
        Point endPosMenuTitle;
        Rectangle dstRectMenuTitle;

        // menu items list empty initially
        List<IMenuItem> menuItems;

        // menu item
        Point[] initPosMenuItem;
        Point[] endPosMenuItem;
        Rectangle[] dstRectMenuItem;

        // menu start position and item separation
        Point MenuStartPosition;
        int MenuItemSeparation;

        // buttons
        Point[] initPosBtn;
        Point[] endPosBtn;
        Rectangle[] dstRectBtn;

        // button messages
        Point[] initPosBtn_Msg;
        Point[] endPosBtn_Msg;
        Rectangle[] dstRectBtn_Msg;

        #endregion

        #region Properties

        /// <summary>
        /// Sets the menu title.
        /// </summary>
        public string MenuTitle
        {
            set
            {
                menuTitle = value;

                // initialization
                Rectangle auxRect = spriteSheet.SourceRectangle(menuTitle);
                dstRectMenuTitle = Rectangle.Empty;
                dstRectMenuTitle.Width = (int)(auxRect.Width * Screen.Scale);
                dstRectMenuTitle.Height = (int)(auxRect.Height * Screen.Scale);
                initPosMenuTitle = new Point((int)(Screen.Width * 0.05f), -dstRectMenuTitle.Height);
                endPosMenuTitle = new Point(initPosMenuTitle.X, (int)(Screen.Height * 0.06f));
                dstRectMenuTitle.Location = initPosMenuTitle;
            }
        }

        /// <summary>
        /// Gets or sets the index of the selected menu item.
        /// </summary>
        public int SelectedItem { get; set; }

        /// <summary>
        /// Gets the total number of menu items.
        /// </summary>
        protected int NumItems { get; private set; }

        /// <summary>
        /// Gets or sets the menu items list.
        /// </summary>
        public List<IMenuItem> MenuItems
        {
            get { return menuItems; }
            set
            {
                // menu items list
                menuItems = value;

                // initialization
                NumItems = menuItems.Count;
                initPosMenuItem = new Point[NumItems];
                endPosMenuItem = new Point[NumItems];
                dstRectMenuItem = new Rectangle[NumItems];

                // obtain menu start position and item separation...
                // ... suppose all labels of equal size
                Rectangle auxRect = spriteSheet.SourceRectangle("ITEM_RESUME");
                Point lblSize = new Point((int)(auxRect.Width * Screen.Scale), (int)(auxRect.Height * Screen.Scale));
                MenuStartPosition = new Point((int)((Screen.Width - lblSize.X) * 0.5f), (int)(Screen.Height * 0.25f));
                MenuItemSeparation = (int)(lblSize.Y * 1.25f);

                // menu items
                for (int i = 0; i < menuItems.Count; i++)
                {
                    // items
                    if (i % 2 == 0)
                        initPosMenuItem[i] = new Point(-(i / 2 + 1) * lblSize.X, MenuStartPosition.Y + MenuItemSeparation * i);
                    else
                        initPosMenuItem[i] = new Point(Screen.Width + (i / 2) * lblSize.X, MenuStartPosition.Y + MenuItemSeparation * i);

                    endPosMenuItem[i] = new Point(MenuStartPosition.X, initPosMenuItem[i].Y);
                    dstRectMenuItem[i] = Rectangle.Empty;
                    dstRectMenuItem[i].Location = initPosMenuItem[i];
                    dstRectMenuItem[i].Width = lblSize.X;
                    dstRectMenuItem[i].Height = lblSize.Y;
                }

                // initialization - buttons
                initPosBtn = new Point[3];
                initPosBtn_Msg = new Point[3];
                endPosBtn = new Point[3];
                endPosBtn_Msg = new Point[3];
                dstRectBtn = new Rectangle[3];
                dstRectBtn_Msg = new Rectangle[3];

                // buttons
                // ... suppose all buttons of equal size
                Rectangle auxBtnRect = spriteSheet.SourceRectangle("BUTTON_A");
                Rectangle auxBtnMsgRect = spriteSheet.SourceRectangle("BUTTON_SELECT");
                Point auxBtnGap = new Point((int)(Screen.Width * 0.05f), (int)((auxBtnRect.Height - auxBtnMsgRect.Height) * Screen.Scale / 2));
                initPosBtn[0] = new Point((int)(Screen.Width * 0.05f), Screen.Height);
                initPosBtn[1] = new Point((int)(Screen.Width * 0.25f), Screen.Height);
                for (int i = 0; i < 2; i++)
                {
                    // buttons
                    endPosBtn[i] = new Point(initPosBtn[i].X, (int)(Screen.Height * 0.9f));
                    dstRectBtn[i] = new Rectangle(initPosBtn[i].X, initPosBtn[i].Y, 0, 0);

                    // button messages
                    initPosBtn_Msg[i] = new Point(initPosBtn[i].X + auxBtnGap.X, initPosBtn[i].Y + auxBtnGap.Y);
                    endPosBtn_Msg[i] = new Point(initPosBtn_Msg[i].X, endPosBtn[i].Y + auxBtnGap.Y);
                    dstRectBtn_Msg[i] = new Rectangle(initPosBtn_Msg[i].X, initPosBtn_Msg[i].Y, 0, 0);
                }
            }
        }

        #endregion

        #region Screen Transitions

        bool toVisible = false;
        bool toHidden = false;
        float trTime = 0.0f;

        /// <summary>
        /// Makes the ScreenComponent2D transition to visible state.
        /// </summary>
        public override void Show()
        {
            toVisible = true;
        }

        /// <summary>
        /// Makes the ScreenComponent2D transition to hidden state.
        /// </summary>
        public override void Hide()
        {
            toHidden = true;
        }

        #endregion

        #region Menu Navigation

        /// <summary>
        /// Defines all possible menu navigation states.
        /// </summary>
        enum MenuState
        {
            MENU_IDLE,
            MENU_WAIT,
            MENU_UP,
            MENU_DOWN
        };

        // current menu navigation state
        MenuState menuState = MenuState.MENU_IDLE;

        // wait for a number of frames before checking player input
        int menuWaitCount;

        // total number of wait frames
        readonly int MenuTotalWaitCount = 10;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="screen">The Screen that this ScreenComponent2D is associated to.</param>
        public GamePlayMenu(StarFoxScreen screen)
            : base(screen)
        {
            // menu initialization
            SelectedItem = 0;
            NumItems = 0;

            #region Asset Loading

            spriteSheet = Game.Content.Load<SpriteSheet>(ContentSettings.GetAsset("MENU_SPRITES"));

            #endregion

            #region Logo

            Rectangle auxRect = spriteSheet.SourceRectangle("LOGO_STARFOX_SMALL");
            dstRectStarFox = new Rectangle((int)(0.675f * Screen.Width), (int)(0.75f * Screen.Height), (int)(auxRect.Width * Screen.Scale), (int)(auxRect.Height * Screen.Scale));
            auxRect = spriteSheet.SourceRectangle("LOGO_XNA_SMALL");
            dstRectXna = new Rectangle(dstRectStarFox.Right - (int)(auxRect.Width * Screen.Scale),
                                       dstRectStarFox.Bottom + (int)(dstRectStarFox.Height * 0.75f),
                                       (int)(auxRect.Width * Screen.Scale), (int)(auxRect.Height * Screen.Scale));

            #endregion
        }

        #endregion

        #region Update

        /// <summary>
        /// Called when the ScreenComponent2D needs to be updated.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        public override void Update(GameTime gameTime)
        {
            #region Player Input

            if (Screen.InputEnabled)
            {
                #region Menu Navigation

                switch (menuState)
                {
                    case MenuState.MENU_IDLE:
                        {
                            // move up
                            if (Screen.PlayerInput.IsMenuUp(Screen.PlayerIndex, true))
                            {
                                menuState = MenuState.MENU_UP;
                            }
                            // move down
                            else if (Screen.PlayerInput.IsMenuDown(Screen.PlayerIndex, true))
                            {
                                menuState = MenuState.MENU_DOWN;
                            }

                            break;
                        }
                    case MenuState.MENU_UP:
                        {
                            #region State Entry Action

                            // go to the previous item
                            SelectedItem--;
                            // if that item is the first, move forward to the end of the list
                            if (SelectedItem == -1)
                                SelectedItem = NumItems - 1;

                            // play a sound
                            Screen.ScreenSound.Cursor();

                            #endregion

                            #region Next State

                            // wait before moving up again
                            menuState = MenuState.MENU_WAIT;

                            #endregion

                            break;
                        }
                    case MenuState.MENU_DOWN:
                        {
                            #region State Entry Action

                            // go to next item
                            SelectedItem++;
                            // if that item is the last, move backward to the beginning of the list
                            if (SelectedItem == NumItems)
                                SelectedItem = 0;

                            // play a sound
                            Screen.ScreenSound.Cursor();

                            #endregion

                            #region Next State

                            // wait before moving down again
                            menuState = MenuState.MENU_WAIT;

                            #endregion

                            break;
                        }
                    case MenuState.MENU_WAIT:
                        {
                            if (menuWaitCount < MenuTotalWaitCount)
                            {
                                // keep waiting...
                                menuWaitCount++;
                            }
                            else
                            {
                                // reset the counter
                                menuWaitCount = 0;

                                // move up again
                                if (Screen.PlayerInput.IsMenuUp(Screen.PlayerIndex, false))
                                {
                                    menuState = MenuState.MENU_UP;
                                }
                                // move down again
                                else if (Screen.PlayerInput.IsMenuDown(Screen.PlayerIndex, false))
                                {
                                    menuState = MenuState.MENU_DOWN;
                                }
                                else
                                {
                                    menuState = MenuState.MENU_IDLE;
                                }
                            }

                            break;
                        }
                    default:
                        break;
                }

                #endregion

                #region Menu Action

                // confirmation
                if (Screen.PlayerInput.IsMenuSelect(Screen.PlayerIndex))
                {
                    Screen.ScreenSound.Accept();
                    OnMenuAccept(SelectedItem);
                }
                // cancellation
                else if (Screen.PlayerInput.IsMenuBack(Screen.PlayerIndex))
                {
                    Screen.ScreenSound.Cancel();
                    Screen.ScreenManager.RemoveScreen(Screen);
                }

                #endregion
            }

            #endregion

            #region Screen Transition

            if (toVisible)
            {
                // update items position
                float auxTrTime = 1.0f - (float)Math.Pow(1.0f - trTime, 2);
                // menu title
                dstRectMenuTitle.Y = initPosMenuTitle.Y + (int)(Math.Abs(initPosMenuTitle.Y - endPosMenuTitle.Y) * auxTrTime);
                // menu items
                for (int i = 0; i < NumItems; i++)
                {
                    if (i % 2 == 0)
                        dstRectMenuItem[i].X = initPosMenuItem[i].X + (int)(Math.Abs(initPosMenuItem[i].X - endPosMenuItem[i].X) * auxTrTime);
                    else
                        dstRectMenuItem[i].X = initPosMenuItem[i].X - (int)(Math.Abs(initPosMenuItem[i].X - endPosMenuItem[i].X) * auxTrTime);
                }
                // buttons
                for (int i = 0; i < 2; i++)
                {
                    dstRectBtn[i].Y = initPosBtn[i].Y - (int)(Math.Abs(initPosBtn[i].Y - endPosBtn[i].Y) * auxTrTime);
                    dstRectBtn_Msg[i].Y = initPosBtn_Msg[i].Y - (int)(Math.Abs(initPosBtn_Msg[i].Y - endPosBtn_Msg[i].Y) * auxTrTime);
                }

                // update transition timer
                float trDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / Time.TotalMilliseconds);
                trTime += trDelta;
                trTime = MathHelper.Clamp(trTime, 0.0f, 1.0f);
                if (trTime == 1.0f) { toVisible = false; trTime = 0.0f; }
            }
            else if (toHidden)
            {
                // update items position
                float auxTrTime = (float)Math.Pow(trTime, 2);
                // menu title
                dstRectMenuTitle.Y = endPosMenuTitle.Y - (int)(Math.Abs(initPosMenuTitle.Y - endPosMenuTitle.Y) * auxTrTime);
                // menu items
                for (int i = 0; i < NumItems; i++)
                {
                    if (i % 2 == 0)
                        dstRectMenuItem[i].X = endPosMenuItem[i].X - (int)(Math.Abs(initPosMenuItem[i].X - endPosMenuItem[i].X) * auxTrTime);
                    else
                        dstRectMenuItem[i].X = endPosMenuItem[i].X + (int)(Math.Abs(initPosMenuItem[i].X - endPosMenuItem[i].X) * auxTrTime);
                }
                // buttons
                for (int i = 0; i < 3; i++)
                {
                    dstRectBtn[i].Y = (int)(endPosBtn[i].Y + Math.Abs(initPosBtn[i].Y - endPosBtn[i].Y) * auxTrTime);
                    dstRectBtn_Msg[i].Y = (int)(endPosBtn_Msg[i].Y + Math.Abs(initPosBtn_Msg[i].Y - endPosBtn_Msg[i].Y) * auxTrTime);
                }

                // update transition timer
                float trDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / Time.TotalMilliseconds);
                trTime += trDelta;
                trTime = MathHelper.Clamp(trTime, 0.0f, 1.0f);
                if (trTime == 1.0f) { toHidden = false; trTime = 0.0f; }
            }

            #endregion
        }

        #endregion

        #region Draw

        /// <summary>
        /// Called when the ScreenComponent2D needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        public override void Draw(GameTime gameTime)
        {
            // logo
            SpriteBatch.Draw(spriteSheet.Texture, dstRectStarFox, spriteSheet.SourceRectangle("LOGO_STARFOX_SMALL"), Color.Red);
            SpriteBatch.Draw(spriteSheet.Texture, dstRectXna, spriteSheet.SourceRectangle("LOGO_XNA_SMALL"), Color.Red);

            // title
            SpriteBatch.Draw(spriteSheet.Texture, dstRectMenuTitle, spriteSheet.SourceRectangle(menuTitle), Color.Yellow);

            #region Menu Items

            for (int i = 0; i < NumItems; i++)
            {
                // color for selected/non-selected items
                Color itemColor = (i == SelectedItem) ? Color.Red : Color.Yellow;
                SpriteBatch.Draw(spriteSheet.Texture, dstRectMenuItem[i], spriteSheet.SourceRectangle(menuItems[i].Label), itemColor);
            }

            #endregion

            #region Buttons

            for (int i = 0; i < menuItems[SelectedItem].Buttons.Length; i++)
            {
                Rectangle auxBtnRect;
                Rectangle auxBtnMsgRect;
                Color auxBtnColor;

                switch (menuItems[SelectedItem].Buttons[i])
                {
                    case ButtonMessage.SELECT:
                        {
                            auxBtnRect = spriteSheet.SourceRectangle("BUTTON_A");
                            auxBtnColor = Color.GreenYellow;
                            auxBtnMsgRect = spriteSheet.SourceRectangle("BUTTON_SELECT");
                            break;
                        }
                    case ButtonMessage.BACK:
                        {
                            auxBtnRect = spriteSheet.SourceRectangle("BUTTON_B");
                            auxBtnColor = Color.Red;
                            auxBtnMsgRect = spriteSheet.SourceRectangle("BUTTON_BACK");
                            break;
                        }
                    default:
                        {
                            auxBtnRect = new Rectangle(0, 0, 0, 0);
                            auxBtnColor = Color.White;
                            auxBtnMsgRect = new Rectangle(0, 0, 0, 0);
                        }
                        break;
                }

                dstRectBtn[i] = new Rectangle(dstRectBtn[i].X, dstRectBtn[i].Y, (int)(auxBtnRect.Width * Screen.Scale), (int)(auxBtnRect.Height * Screen.Scale));

                dstRectBtn_Msg[i] = new Rectangle(dstRectBtn_Msg[i].X, dstRectBtn_Msg[i].Y, (int)(auxBtnMsgRect.Width * Screen.Scale), (int)(auxBtnMsgRect.Height * Screen.Scale));

                SpriteBatch.Draw(spriteSheet.Texture, dstRectBtn[i], auxBtnRect, auxBtnColor);
                SpriteBatch.Draw(spriteSheet.Texture, dstRectBtn_Msg[i], auxBtnMsgRect, Color.Yellow);
            }

            #endregion
        }

        #endregion

        #region Events

        /// <summary>
        /// Raised when a menu accept action is required.
        /// </summary>
        public event EventHandler<MenuEventArgs> MenuAccept;

        /// <summary>
        /// Raises the MenuAccept event.
        /// <para>Override this method with menu-specific action code.</para>
        /// </summary>
        /// <param name="index">Index of the selected item.</param>
        protected virtual void OnMenuAccept(int index)
        {
            if (MenuAccept != null)
                MenuAccept(this, new MenuEventArgs(index));
        }

        /// <summary>
        /// Defines a custom event argument which includes the index of the selected item.
        /// <para>This is used by the events defined in ApplicationMenu class.</para>
        /// </summary>
        public class MenuEventArgs : EventArgs
        {
            #region Properties

            /// <summary>
            /// Gets the index of the selected item.
            /// </summary>
            public int? Index
            {
                get;
                private set;
            }

            #endregion

            #region Constructor

            /// <summary>
            /// Initializes a new instance of this class.
            /// </summary>
            /// <param name="index">Index of the selected item.</param>
            public MenuEventArgs(int? index)
            {
                Index = index;
            }

            #endregion
        }

        #endregion
    }
}
